Bernstein, D and Lagakos, SW. Sample size and power determination for stratified clinical trials. Journal of Statistical Computation Simulation, 8:65-73, 1978.
The Two Arm Survival calculator computes an estimate of total sample size and either accrual rate or power for a comparison of survival between two arms.
The calculations are based on the assumptions of uniform accrual over time, no loss to follow-up, exponentially distributed event times, large sample approximation, and use of the exponential MLE test.
The user is prompted for the type of calculation to be performed, either a calculation of sample size required or a calculation of power. The user can also perform the calculation over a specified number of strata (maximum 6). The default setting is to estimate the power for a single stratum. The user is then prompted for the inputs listed below.
All inputs must be in terms of the same time units (e.g. years, months, etc).
For survival calculations involving more than one stratum, all calculations will be performed such that the hazard ratio will remain equal between all strata. For input type of hazard ratio, the hazard ratio input will be used for all strata; for input types of survival proportion and median survival, only the first strata will have prompts for estimates on the experimental arm, and the hazard ratio calculated from these estimates will be automatically applied to the additional strata.
Some input items have initial default values, as indicated in parentheses below.
The user is prompted for values to the following items. For items that have initial default values set, the values are given in parentheses.
If input type is hazard ratio:
If input type is survival proportions:
If input type is median survival:
Within each stratum, the following will also be output:
If input type is hazard ratio:
If input type is survival proportions:
If input type is median survival:
The program is written in R.
View Code
function(calc_type = "Sample Size", input_type = "Hazard Ratio", p_s, sides, accrual, followup, pstrat = 1, p0, t0, hr, p1, t1, accrualRate, alpha, power) {
if (input_type == "Survival Proportions") {
hr = (-log(p0) / t0) / (-log(p1) / t1)
}
if (input_type == "Median Survival") {
hr = (-log(p0) / t0) / (-log(p1) / t1)
}
alpha = alpha / sides
zalpha = qnorm(alpha)
nstrata = length(p0)
hre_s = array(nstrata)
hre_e = array(nstrata)
ps_e = array(nstrata)
ms_s = array(nstrata)
ms_e = array(nstrata)
st_e = array(nstrata)
cpie = array(nstrata)
epie = array(nstrata)
stratvalues = matrix(, nrow = nstrata, ncol = 6)
for (i in 1:nstrata) {
hre_s[i] = -log(p0[i]) / t0[i]
hre_e[i] = (-log(p0[i]) / t0[i]) / hr
ps_e[i] = exp(-hre_e[i] * t0[i])
ms_s[i] = -log(0.5) / hre_s[i]
ms_e[i] = -log(0.5) / hre_e[i]
if(input_type == "Hazard Ratio") {st_e[i] = t0[i]}
else if(input_type == "Survival Proportions" | input_type == "Median Survival") {st_e[i] = t1[i]}
stratvalues[i, 1] = round(hre_s[i], 4)
stratvalues[i, 2] = round(hre_e[i], 4)
stratvalues[i, 3] = round(ps_e[i], 3)
stratvalues[i, 4] = round(ms_s[i], 3)
stratvalues[i, 5] = round(ms_e[i], 3)
stratvalues[i, 6] = round(st_e[i], 3)
cpie[i] = 1 - (exp(-hre_s[i] * followup) - exp(-hre_s[i] * accrual - hre_s[i] * followup)) / (accrual * hre_s[i])
epie[i] = 1 - (exp(-hre_e[i] * followup) - exp(-hre_e[i] * accrual - hre_e[i] * followup)) / (accrual * hre_e[i])
}
wn = 0
wa = 0
if (calc_type == "Sample Size") {
#sample size calculation given input of power
for (i in 1:nstrata) {
f = pstrat[i] * accrual
fc = f * p_s
fe = f - fc
#set varN, varA to same calculation (alt-alt variance term)
#this matches the formula in the Bernstein, Lagakos manuscript
varN = 1 / (fc * cpie[i]) + 1 / (fe * epie[i])
varA = 1 / (fc * cpie[i]) + 1 / (fe * epie[i])
wn = wn + 1 / varN
wa = wa + 1 / varA
}
vn = 1 / wn
va = 1 / wa
zalpha = -zalpha
crit = zalpha * sqrt(vn)
zbeta = qnorm(1 - power)
accrualRate = (crit - zbeta * sqrt(va)) / log(hr)
accrualRate = round(accrualRate * accrualRate, 2)
totalAccrual = floor(accrualRate * accrual)
} else if (calc_type == "Power") {
#power calculation given inputs of sample size, accrual rate
for (i in 1:nstrata) {
f = accrualRate * pstrat[i] * accrual
fc = f * p_s
fe = f - fc
#set varN, varA to same calculation (alt-alt variance term)
#this matches the formula in the Bernstein, Lagakos manuscript
varN = 1 / (fc * cpie[i]) + 1 / (fe * epie[i])
varA = 1 / (fc * cpie[i]) + 1. / (fe * epie[i])
wn = wn + 1 / varN
wa = wa + 1 / varA
}
vn = 1 / wn
va = 1 / wa
zalpha = -zalpha
crit = zalpha * sqrt(vn)
power = round(1 - pnorm((crit - log(hr)) / sqrt(va)), 4)
totalAccrual = floor(accrualRate * accrual)
}
calcvalues = data.frame(Power = power,
Sample_Size = totalAccrual,
Accrual_Rate = accrualRate,
Hazard_Ratio = hr)
stratvalues = as.data.frame(stratvalues)
names(stratvalues) = c("Hazard_Rate_Std", "Hazard_Rate_Exp", "Prop_Surviving_Exp",
"Median_Survival_Std", "Median_Survival_Exp", "Survival_Time_Exp")
output = list(calcvalues = calcvalues,
stratvalues = stratvalues)
return(jsonlite::toJSON(output, pretty = TRUE))
}